﻿using System;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;

using Job.Core.Common;

namespace Job.Core.Web
{
    /// <summary>
    /// IP 地址库操作(无法继承此类)。
    /// 纯真IP库，更新地址 http://www.cz88.net/ 。
    /// 考虑使用单例模式使用此类。
    /// </summary>
    public sealed class IPData
    {

        #region Properties

        private byte[] data;
        private long firstStartIpOffset, lastStartIpOffset, ipCount;
        private string fileName;

        #endregion

        #region Constructor

        /// <summary>
        /// 初始化 IPData 类的新实例。
        /// </summary>
        /// <param name="fileName">IP库文件全名。</param>
        public IPData(string fileName)
        {
            if (string.IsNullOrEmpty(fileName))
                throw new ArgumentNullException("fileName");

            if (!File.Exists(fileName))
                throw new FileNotFoundException(string.Empty, fileName);

            this.fileName = fileName;

            using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read))
            {
                data = new byte[fs.Length];
                fs.Read(data, 0, data.Length);
            }

            byte[] buffer = new byte[8];
            Array.Copy(data, 0, buffer, 0, 8);
            firstStartIpOffset = ((buffer[0] + (buffer[1] * 0x100)) + ((buffer[2] * 0x100) * 0x100)) + (((buffer[3] * 0x100) * 0x100) * 0x100);
            lastStartIpOffset = ((buffer[4] + (buffer[5] * 0x100)) + ((buffer[6] * 0x100) * 0x100)) + (((buffer[7] * 0x100) * 0x100) * 0x100);
            ipCount = Convert.ToInt64((double)(((double)(lastStartIpOffset - firstStartIpOffset)) / 7.0));
        }

        #endregion

        #region Methods

        /// <summary>
        /// 获取当前 IP 地址区域信息。
        /// </summary>
        /// <returns>区域信息。</returns>
        public string GetAddress()
        {
            return GetAddress(HttpContext.Current.Request.UserHostAddress);
        }

        /// <summary>
        /// 获取指定 IP 地址区域信息。
        /// </summary>
        /// <param name="ip">IP 地址。</param>
        /// <returns>区域信息。</returns>
        public string GetAddress(string ip)
        {
            if (string.IsNullOrEmpty(ip))
                return string.Empty;

            Location loc = GetLocation(ip);
            return loc.Area + loc.Detail;
        }

        /// <summary>
        /// 获取当前 IP 地址区域信息。
        /// </summary>
        /// <param name="province">out 省。</param>
        /// <param name="city">out 市。</param>
        /// <returns>区域信息。</returns>
        public string GetAddress(out string province, out string city)
        {
            return GetAddress(HttpContext.Current.Request.UserHostAddress, out province, out city);
        }

        /// <summary>
        /// 获取指定 IP 地址区域信息。
        /// </summary>
        /// <param name="ip">IP 地址。</param>
        /// <param name="province">out 省。</param>
        /// <param name="city">out 市。</param>
        /// <returns>区域信息。</returns>
        public string GetAddress(string ip, out string province, out string city)
        {
            if (string.IsNullOrEmpty(ip))
            {
                province = string.Empty;
                city = string.Empty;
                return string.Empty;
            }

            Location loc = GetLocation(ip);
            province = loc.Province;
            city = loc.City;
            return loc.Area + loc.Detail;
        }

        /// <summary>
        /// 获取当前 IP 地址区域信息。
        /// </summary>
        /// <returns>区域信息实例。</returns>
        public Location GetLocation()
        {
            return GetLocation(HttpContext.Current.Request.UserHostAddress);
        }

        /// <summary>
        /// 获取指定 IP 地址区域信息。
        /// </summary>
        /// <param name="ip">IP 地址。</param>
        /// <returns>区域信息实例。</returns>
        public Location GetLocation(string ip)
        {
            if (!ip.IsIP())
                throw new ArgumentException("IP 格式错误。");

            Location Location = new Location { IP = ip };

            long intIP = IpToInt(ip);
            if ((intIP >= IpToInt("127.0.0.1") && (intIP <= IpToInt("127.255.255.255"))))
            {
                Location.Area = "本机/局域网地址";
                Location.Detail = string.Empty;
            }
            else
            {
                if ((((intIP >= IpToInt("0.0.0.0")) && (intIP <= IpToInt("2.255.255.255"))) || ((intIP >= IpToInt("64.0.0.0")) && (intIP <= IpToInt("126.255.255.255")))) ||
                ((intIP >= IpToInt("58.0.0.0")) && (intIP <= IpToInt("60.255.255.255"))))
                {
                    Location.Area = "网络保留地址";
                    Location.Detail = string.Empty;
                }
            }

            long right = ipCount;
            long left = 0L;
            long middle = 0L;
            long startIp = 0L;
            long endIpOff = 0L;
            long endIp = 0L;
            int countryFlag = 0;
            while (left < (right - 1L))
            {
                middle = (right + left) / 2L;
                startIp = GetStartIp(middle, out endIpOff);
                if (intIP == startIp)
                {
                    left = middle;
                    break;
                }
                if (intIP > startIp)
                {
                    left = middle;
                }
                else
                {
                    right = middle;
                }
            }

            startIp = GetStartIp(left, out endIpOff);
            endIp = GetEndIp(endIpOff, out countryFlag);

            if ((startIp <= intIP) && (endIp >= intIP))
            {
                string local;
                Location.Area = GetCountry(endIpOff, countryFlag, out local);
                Location.Detail = local;
            }
            else
            {
                Location.Area = "未知";
                Location.Detail = string.Empty;
            }

            return Location;
        }

        private long GetStartIp(long left, out long endIpOff)
        {
            long leftOffset = firstStartIpOffset + (left * 7L);
            byte[] buffer = new byte[7];
            System.Array.Copy(data, leftOffset, buffer, 0, 7);
            endIpOff = (Convert.ToInt64(buffer[4].ToString()) + (Convert.ToInt64(buffer[5].ToString()) * 0x100L)) + ((Convert.ToInt64(buffer[6].ToString()) * 0x100L) * 0x100L);
            return ((Convert.ToInt64(buffer[0].ToString()) + (Convert.ToInt64(buffer[1].ToString()) * 0x100L)) + ((Convert.ToInt64(buffer[2].ToString()) * 0x100L) * 0x100L)) + (((Convert.ToInt64(buffer[3].ToString()) * 0x100L) * 0x100L) * 0x100L);
        }

        private long GetEndIp(long endIpOff, out int countryFlag)
        {
            byte[] buffer = new byte[5];
            System.Array.Copy(data, endIpOff, buffer, 0, 5);
            countryFlag = buffer[4];
            return ((Convert.ToInt64(buffer[0].ToString()) + (Convert.ToInt64(buffer[1].ToString()) * 0x100L)) + ((Convert.ToInt64(buffer[2].ToString()) * 0x100L) * 0x100L)) + (((Convert.ToInt64(buffer[3].ToString()) * 0x100L) * 0x100L) * 0x100L);
        }

        private string GetCountry(long endIpOff, int countryFlag, out string local)
        {
            string country = string.Empty;
            long offset = endIpOff + 4L;
            switch (countryFlag)
            {
                case 1:
                case 2:
                    country = GetFlagStr(ref offset, ref countryFlag, ref endIpOff);
                    offset = endIpOff + 8L;
                    local = (1 == countryFlag) ? string.Empty : GetFlagStr(ref offset, ref countryFlag, ref endIpOff);
                    break;
                default:
                    country = GetFlagStr(ref offset, ref countryFlag, ref endIpOff);
                    local = GetFlagStr(ref offset, ref countryFlag, ref endIpOff);
                    break;
            }
            return country;
        }

        private string GetFlagStr(ref long offset, ref int countryFlag, ref long endIpOff)
        {
            int flag = 0;
            byte[] buffer = new byte[3];

            while (true)
            {
                long forwardOffset = offset;
                flag = data[forwardOffset++];
                
                if (flag != 1 && flag != 2)
                {
                    break;
                }
                System.Array.Copy(data, forwardOffset, buffer, 0, 3);
                forwardOffset += 3;
                if (flag == 2)
                {
                    countryFlag = 2;
                    endIpOff = offset - 4L;
                }
                offset = (Convert.ToInt64(buffer[0].ToString()) + (Convert.ToInt64(buffer[1].ToString()) * 0x100L)) + ((Convert.ToInt64(buffer[2].ToString()) * 0x100L) * 0x100L);
            }
            if (offset < 12L)
            {
                return string.Empty;
            }
            return GetStr(ref offset);
        }

        private string GetStr(ref long offset)
        {
            byte lowByte = 0;
            byte highByte = 0;
            StringBuilder stringBuilder = new StringBuilder();
            byte[] bytes = new byte[2];
            Encoding encoding = Encoding.GetEncoding("GB2312");
            while (true)
            {
                lowByte = data[offset++];
                if (lowByte == 0)
                {
                    return stringBuilder.ToString();
                }
                if (lowByte > 0x7f)
                {
                    highByte = data[offset++];
                    bytes[0] = lowByte;
                    bytes[1] = highByte;
                    if (highByte == 0)
                    {
                        return stringBuilder.ToString();
                    }
                    stringBuilder.Append(encoding.GetString(bytes));
                }
                else
                {
                    stringBuilder.Append((char)lowByte);
                }
            }
        }

        /// <summary>
        /// 将 IP 地址转换成 long 形式的值。
        /// </summary>
        /// <param name="ip"></param>
        /// <returns></returns>
        private long IpToInt(string ip)
        {
            char[] separator = new char[] { '.' };
            if (ip.Split(separator).Length == 3)
            {
                ip = ip + ".0";
            }
            string[] strArray = ip.Split(separator);
            long num2 = ((long.Parse(strArray[0]) * 0x100L) * 0x100L) * 0x100L;
            long num3 = (long.Parse(strArray[1]) * 0x100L) * 0x100L;
            long num4 = long.Parse(strArray[2]) * 0x100L;
            long num5 = long.Parse(strArray[3]);
            return (((num2 + num3) + num4) + num5);
        }

        /// <summary>
        /// 将 long 值表示的 IP 地址转换为实际显示的 IP 地址字串。
        /// </summary>
        /// <param name="ip"></param>
        /// <returns></returns>
        private string IntToIP(long ip)
        {
            long num = (long)((ip & 0xff000000L) >> 0x18);

            if (num < 0L)
                num += 0x100L;

            long num2 = (ip & 0xff0000L) >> 0x10;

            if (num2 < 0L)
                num2 += 0x100L;

            long num3 = (ip & 0xff00L) >> 8;

            if (num3 < 0L)
                num3 += 0x100L;

            long num4 = ip & 0xffL;

            if (num4 < 0L)
                num4 += 0x100L;

            return (num.ToString() + "." + num2.ToString() + "." + num3.ToString() + "." + num4.ToString());
        }

        #endregion

    }

    #region Location

    /// <summary>
    /// 域名位置信息。
    /// </summary>
    public class Location
    {
        /// <summary>
        /// IP 地址。
        /// </summary>
        public string IP { get; set; }

        private string province;
        /// <summary>
        /// 省。
        /// </summary>
        public string Province
        {
            get
            {
                Regex r = new Regex(".*省|广西|内蒙古|宁夏|新疆|西藏");
                return r.Match(Area).Value;
            }
            set { province = value; }
        }
        
        /// <summary>
        /// 城市。
        /// </summary>
        public string City
        {
            get
            {
                if (string.IsNullOrEmpty(Province))
                    return string.Empty;

                return Area.Replace(Province, string.Empty);                
            }
        }

        /// <summary>
        /// 区域。
        /// </summary>
        public string Area { get; set; }

        /// <summary>
        /// 详细位置。
        /// </summary>
        public string Detail { get; set; }
    }

    #endregion
}
